import {
  JsonToTsOptions,
  JsonValueType,
  TsTypeDefinition,
  TsTypeNode,
} from "./type";
import {
  detectType,
  generateRootTypeName,
  generateTypeName,
  indent,
  isValidIdentifier,
  safeParseJson,
} from "./util";

/**
 * Default options
 */
const DEFAULT_OPTIONS: Required<JsonToTsOptions> = {
  rootName: "Root",
  format: true,
  indentSpaces: 2,
  useInterface: false,
  optionalProperties: false,
};

/**
 * Analyze JSON object and build type node tree
 * @param data JSON object
 * @param name Node name
 * @param parent Parent node
 * @returns Type node
 */
function parseJsonToTypeNode(
  data: unknown,
  name: string,
  parent?: TsTypeNode
): TsTypeNode {
  const type = detectType(data);
  const node: TsTypeNode = { name, type, parent: parent || undefined };

  if (type === JsonValueType.ARRAY && Array.isArray(data)) {
    // Handle array type
    if (data.length === 0) {
      // Empty array, use unknown[]
      node.arrayElementType = {
        name: "item",
        type: JsonValueType.NULL,
        parent: node,
      };
    } else {
      // Analyze array elements
      const elementTypes = data.map(item => detectType(item));
      const uniqueTypes = [...new Set(elementTypes)];

      if (uniqueTypes.length === 1) {
        // All elements have the same type
        const elementType = uniqueTypes[0];
        const elementName = `${name}Item`;

        if (
          elementType === JsonValueType.OBJECT ||
          elementType === JsonValueType.ARRAY
        ) {
          // If it's an object or array, we only analyze the first element as a representative
          node.arrayElementType = parseJsonToTypeNode(
            data[0],
            elementName,
            node
          );
        } else {
          // 基本类型
          node.arrayElementType = {
            name: elementName,
            type: elementType as JsonValueType,
            parent: node,
          };
        }
      } else {
        // Mixed type array, create union type
        node.arrayElementType = {
          name: `${name}Item`,
          type: JsonValueType.NULL, // NULL is used here to represent union type
          parent: node,
          children: data.map((item, index) => {
            return parseJsonToTypeNode(item, `${name}Item${index}`, node);
          }),
        };
      }
    }
  } else if (
    type === JsonValueType.OBJECT &&
    data !== null &&
    typeof data === "object"
  ) {
    // Handle object type
    const properties: Record<string, TsTypeNode> = {};

    for (const [key, value] of Object.entries(data)) {
      // Generate type name using key name
      const propName = generateTypeName(key, detectType(value));
      properties[key] = parseJsonToTypeNode(value, propName, node);
    }

    node.properties = properties;
  }

  return node;
}

/**
 * Generate TypeScript type definition
 * @param node Type node
 * @param options Options
 * @param level Indentation level
 * @returns Type definition object
 */
function generateTypeDefinition(
  node: TsTypeNode,
  options: Required<JsonToTsOptions>,
  level: number = 0
): TsTypeDefinition {
  const { optionalProperties, indentSpaces } = options;
  const childDefinitions: TsTypeDefinition[] = [];
  let definition = "";

  switch (node.type) {
    case JsonValueType.NULL:
      definition = "null";
      break;

    case JsonValueType.BOOLEAN:
      definition = "boolean";
      break;

    case JsonValueType.NUMBER:
      definition = "number";
      break;

    case JsonValueType.STRING:
      definition = "string";
      break;

    case JsonValueType.ARRAY:
      if (node.arrayElementType) {
        if (
          node.arrayElementType.type === JsonValueType.NULL &&
          node.arrayElementType.children
        ) {
          // Union type array
          const unionTypes = node.arrayElementType.children.map(child => {
            const childDef = generateTypeDefinition(child, options, level);
            childDefinitions.push(...(childDef.children || []));
            return childDef.definition;
          });
          definition = `(${unionTypes.join(" | ")})[]`;
        } else if (
          node.arrayElementType.type === JsonValueType.OBJECT ||
          node.arrayElementType.type === JsonValueType.ARRAY
        ) {
          // Array of object or array type
          const childDef = generateTypeDefinition(
            node.arrayElementType,
            options,
            0
          );
          childDefinitions.push(childDef);
          childDefinitions.push(...(childDef.children || []));
          definition = `${childDef.name}[]`;
        } else {
          // Basic type array
          const elementType = generateTypeDefinition(
            node.arrayElementType,
            options,
            level
          );
          definition = `${elementType.definition}[]`;
        }
      } else {
        // Empty array or unknown type
        definition = "unknown[]";
      }
      break;

    case JsonValueType.OBJECT:
      if (node.properties && Object.keys(node.properties).length > 0) {
        const propertyDefinitions: string[] = [];

        for (const [key, propNode] of Object.entries(node.properties)) {
          const propIndent = options.format
            ? indent(level + 1, indentSpaces)
            : "";
          const propSuffix = optionalProperties ? "?" : "";

          if (
            propNode.type === JsonValueType.OBJECT ||
            propNode.type === JsonValueType.ARRAY
          ) {
            // For object and array properties, generate separate types
            const propDef = generateTypeDefinition(propNode, options, 0);
            childDefinitions.push(propDef);
            childDefinitions.push(...(propDef.children || []));
            propertyDefinitions.push(
              `${propIndent}${isValidIdentifier(key) ? key : JSON.stringify(key)}${propSuffix}: ${propDef.name}`
            );
          } else {
            // Basic type properties
            const propDef = generateTypeDefinition(
              propNode,
              options,
              level + 1
            );
            propertyDefinitions.push(
              `${propIndent}${isValidIdentifier(key) ? key : JSON.stringify(key)}${propSuffix}: ${propDef.definition}`
            );
          }
        }

        const openBrace = options.format ? " {" : " {";
        const closeBrace = options.format
          ? `\n${indent(level, indentSpaces)}}`
          : " }";
        const separator = options.format ? ";\n" : "; ";

        definition = `${openBrace}\n${propertyDefinitions.join(separator)};${closeBrace}`;
      } else {
        // Empty object
        definition = "{}";
      }
      break;
  }

  return {
    name: node.name,
    definition,
    children: childDefinitions.length > 0 ? childDefinitions : [],
  };
}

/**
 * Generate final TypeScript type definition string
 * @param typeDefinitions Array of type definition objects
 * @param options Options
 * @returns TypeScript type definition string
 */
function generateTypeScript(
  typeDefinitions: TsTypeDefinition[],
  options: Required<JsonToTsOptions>
): string {
  const { useInterface, format } = options;
  const lines: string[] = [];

  // 生成类型定义
  for (const typeDef of typeDefinitions) {
    const keyword =
      useInterface && typeDef.definition.startsWith(" {")
        ? "interface"
        : "type";
    const separator =
      useInterface && typeDef.definition.startsWith(" {") ? "" : " = ";

    // Handle the case where = is followed by {, avoid extra spaces
    const formattedDefinition = typeDef.definition;
    if (
      separator === " = " &&
      (formattedDefinition.startsWith(" {") ||
        formattedDefinition.startsWith("{"))
    ) {
      // If = is followed by {, remove the space after =
      const line = `${keyword} ${typeDef.name} =${formattedDefinition};`;
      lines.push(line);
    } else {
      const line = `${keyword} ${typeDef.name}${separator}${formattedDefinition};`;
      lines.push(line);
    }

    if (format) {
      lines.push(""); // Add empty line to separate different types
    }
  }

  return lines.join(format ? "\n" : " ");
}

/**
 * Convert JSON to TypeScript type definition
 * @param jsonInput JSON string or object
 * @param options Options
 * @returns TypeScript type definition string
 */
export function jsonToTs(
  jsonInput: string | unknown,
  options?: JsonToTsOptions
): string {
  // Merge options
  const mergedOptions: Required<JsonToTsOptions> = {
    ...DEFAULT_OPTIONS,
    ...options,
  };

  try {
    // Parse JSON input
    const data =
      typeof jsonInput === "string" ? safeParseJson(jsonInput) : jsonInput;

    // Detect root type
    const rootType = detectType(data);
    const rootName = generateRootTypeName(rootType, mergedOptions.rootName);

    // Build type node tree
    const rootNode = parseJsonToTypeNode(data, rootName);

    // Generate type definition
    const rootDef = generateTypeDefinition(rootNode, mergedOptions);

    // Collect all type definitions
    const allDefs = [rootDef, ...(rootDef.children || [])];

    // Generate final TypeScript code
    return generateTypeScript(allDefs, mergedOptions);
  } catch (error) {
    throw new Error(`JSON to TS type conversion failed: ${(error as Error).message}`);
  }
}

// Export types and utility functions
export * from "./type";
export * from "./util";
